package edu.gatech.coc.sirana.detection;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.StringTokenizer;

import net.util.Debug;
import net.util.File_Util;
import net.util.Out;

import edu.gatech.coc.sirana.exception.SIRANAException;
import edu.gatech.coc.sirana.util.CONSTANT;

public class ClusterOperator {
	
	ArrayList<AttributeCluster> KMeansClusteringTest() throws IOException{
		String dataSrc = CONSTANT.TESTING_ATTR;
		ArrayList<SRCycleAttribute> objects = createTestingObjects(dataSrc);	
		
//		for (SRCycleAttribute o: objects)
//			Debug.println(o);
//		Debug.exit();
		
		ArrayList<AttributeCluster> decisionClusters = new ArrayList<AttributeCluster>();
		decisionClusters.addAll(clusterHealthyData());
		decisionClusters.addAll(clusterFailureData());
		
		Out.println("Decision processing...");
		this.assignCluster(decisionClusters, objects);
		
		
		for (AttributeCluster c : decisionClusters){
			Out.println("---cluster---\n" + c.toString());
	}
		
		return decisionClusters;
	}
	
	void probabiliticalClusterTestingData() throws IOException{
		String dataSrc = CONSTANT.TESTING_ATTR;
		ArrayList<SRCycleAttribute> objects = createTestingObjects(dataSrc);	
		
//		for (SRCycleAttribute o: objects)
//			Debug.println(o);
//		Debug.exit();
		
		ArrayList<AttributeCluster> hClusters =clusterHealthyData();
		ArrayList<AttributeCluster> fClusters = clusterFailureData();
		
		Out.println("Decision processing...");
		
		for (SRCycleAttribute o : objects){
			BayesianClassify(o, fClusters, hClusters);
		}
		
//		Debug.exit();
//		Out.println("----Healthy clusters----");
//		for (AttributeCluster h : hClusters){
//			Out.println(h.toString());
//		}
//		
//		Out.println("----Failure clusters----");
//		for (AttributeCluster f : fClusters){
//			Out.println(f.toString());
//		}
	}
	
	private void BayesianClassify(SRCycleAttribute o, ArrayList<AttributeCluster> fClusters, ArrayList<AttributeCluster> hClusters) {
		AttributeCluster fc = this.findCluster(o, fClusters);
		AttributeCluster hc = this.findCluster(o, hClusters);
		
		double fCDist = this.computeDistanceToCluster(o, fc);
		double hCDist = this.computeDistanceToCluster(o, hc);
		
		if (fCDist==0 && hCDist==0){
			fCDist = CONSTANT.epsilon;
			hCDist = CONSTANT.epsilon;
		}
		
		double probF = (hCDist)/(hCDist + fCDist);
		double probH = (fCDist)/(hCDist + fCDist);
		
		double oFFreq = computeObjectFreq(o, fClusters);
		double oHFreq = computeObjectFreq(o, hClusters);
		
		double postF = probF * oFFreq;
		double postH = probH * oHFreq;
		
		if (postH > postF) Out.print( o.getId() + "; H" );
		else  Out.print( o.getId() + "; F");
		Out.println( "\t{" + o.toString() + "}" );
		
	}
	
	private void probClusteringTest(SRCycleAttribute o, ArrayList<AttributeCluster> fClusters, ArrayList<AttributeCluster> hClusters) {
		AttributeCluster fc = this.findCluster(o, fClusters);
		AttributeCluster hc = this.findCluster(o, hClusters);
		
		Debug.println("----------");
		Debug.println( o.toString() + ":\nFC ::" + fc );
		Debug.println( o.toString() + ":\nHC ::" + hc );
		
		//for testing
		ArrayList<AttributeCluster> allC = new ArrayList<AttributeCluster>();
		allC.addAll(fClusters);
		allC.addAll(hClusters);
		AttributeCluster ac = this.findCluster(o, allC);
		Debug.println( o.toString() + ":\nAC ::" + ac );
		//for testing end
		
		double oFSRFreq = computeObjectFreq(o, fClusters);
		double oHSRFreq = computeObjectFreq(o, hClusters);
		double sumFreq = oFSRFreq + oHSRFreq;
		
		double normFSRFreq = oFSRFreq / sumFreq;
		double normHSRFreq = oHSRFreq / sumFreq;
		if (oFSRFreq==0 && oHSRFreq==0) {
			normFSRFreq = 1;
			normHSRFreq = 1;
		}
		
		Debug.println("normFSRFreq =" + normFSRFreq);
		Debug.println("normHSRFreq =" + normHSRFreq);
		
		double fCDist = this.computeDistanceToCluster(o, fc);
		double hCDist = this.computeDistanceToCluster(o, hc);
		if (fCDist==hCDist) {
			fCDist = 1;
			hCDist = 1;
		}
		
		Debug.println("fCDist =" + fCDist);
		Debug.println("hCDist =" + hCDist);
		
		double fDist =  fCDist/ normFSRFreq;
		double hDist =  hCDist/ normHSRFreq;
		
		
		
		if (hDist < fDist) {
			hc.addElement(o);
			Debug.println("===" + o.toString() + "t\n added to " + hc);
		}
//		else if(hDist == fDist){
//			if (hc.getElements().size() > fc.getElements().size()) {
//				hc.addElement(o);
//				Debug.println("===" + o.toString() + "t\n added to " + hc);
//			}else{
//				fc.addElement(o);
//				Debug.println("===" + o.toString() + "t\n added to " + hc);
//			}
//		}
		else {
			fc.addElement(o);
			Debug.println("===" + o.toString() + "t\n added to " + fc);
		}
	}

	private double computeObjectFreq(SRCycleAttribute o, ArrayList<AttributeCluster> fClusters) {
		int occur = 0;
		int total = 0;
		
		String oEventString = o.findEventString();
		for (AttributeCluster fc : fClusters){
			ArrayList<SRCycleAttribute> objects = fc.getElements();
			total += objects.size();
			for (SRCycleAttribute _o : objects){
				String fcEventString = _o.findEventString();
				if (oEventString.equals(fcEventString)) occur++;
			}
		}
		
		return (double)occur/total;
	}

	ArrayList<SRCycleAttribute> createTestingObjects(String dataSrc) throws IOException{
		Debug.println("createTestingObjects...");
		ArrayList<SRCycleAttribute> objects = new  ArrayList<SRCycleAttribute>();
		ArrayList<String> dataStr = File_Util.file_content_to_strings(dataSrc);
		
		for(String ds : dataStr){
			SRCycleAttribute sa = createAttribute(ds, CONSTANT.KNOW_TEST);	
			sa.setId(sa.getId() + "t");
			objects.add(sa);
		}
		
		return objects;
	}	
	
		
	ArrayList<AttributeCluster> clusterFailureData() throws IOException{
		int isKnownFailure = CONSTANT.KNOW_FAILURE;
		ArrayList<SRCycleAttribute> objects = createTrainingObjects(
															//CONSTANT.TRAINING_ATTR_FAILURE,
															CONSTANT.TESTCASE,
															isKnownFailure);		
	
		ArrayList<AttributeCluster> clusters = this.initializeTrainingClusters(objects, isKnownFailure);
		this.partitionClusters(clusters, objects);
		
		for (AttributeCluster c : clusters){
			Debug.println("---failure cluster---\n" + c.toString());
		}
		return clusters;
	} 
	
	ArrayList<AttributeCluster> clusterHealthyData() throws IOException{
		int isKnownFailure = CONSTANT.KNOW_PASS;
		//need to change the following.
		ArrayList<SRCycleAttribute> objects		= createTrainingObjects(CONSTANT.TRAINING_ATTR_HEALTHY, isKnownFailure);
		Debug.println(objects.size() + " objects to partition.");
		ArrayList<AttributeCluster> clusters	= this.initializeTrainingClusters(objects, isKnownFailure);
		
//		Debug.println("\n======doCluster=========");
		partitionClusters(clusters, objects);
		
		for (AttributeCluster c : clusters){
			Debug.println("---healthy cluster---\n" + c.toString());
		}
		
		return clusters;
	}
	
		
	private void partitionClusters(ArrayList<AttributeCluster> clusters, ArrayList<SRCycleAttribute> objects) {
		this.assignCluster(clusters, objects);		
		updateClusters(clusters);	
	}

	void updateClusters(ArrayList<AttributeCluster> clusters){
		ArrayList<SRCycleAttribute> elements = new ArrayList<SRCycleAttribute>();
		for (AttributeCluster ac : clusters){
			updateCenter( ac );
			elements.addAll(ac.getElements());
			ac.setElements(new ArrayList<SRCycleAttribute>()); 
		}
		
// 		Debug.println(elements.size() + " elements to reassign");
		assignCluster(clusters, elements);
	}
	
	/**
	 * use the mode string, the most frequent string, as the new center.
	 * @param ac
	 */
	private void updateCenter(AttributeCluster ac) {
		HashMap<String, Integer> occMap = new HashMap<String, Integer>();
		for (SRCycleAttribute sa : ac.getAllElements()){
			String events = sa.getEvents().toString();
			if (occMap.keySet().contains(events)){
				int occ = occMap.get(events);
				occ++;
				occMap.put(events, occ);
			}else{
				occMap.put(events, 1);
			}
		}
		
		int tmpFreq	= occMap.get(ac.getCenter().getEvents().toString());
		String tmpCenter = ac.getCenter().getEvents().toString();
		for (String sa : occMap.keySet()){
			int occ = occMap.get(sa);
			if (occ > tmpFreq) tmpCenter = sa; 
		}
		
		SRCycleAttribute center = ac.getCenter();
		SRCycleAttribute newCenter = null;
		for (SRCycleAttribute sa : ac.getElements()){
			if (sa.getEvents().toString().equals(tmpCenter)){
				if (tmpCenter != sa.getEvents().toString()){
					ac.setCenter(sa);
					newCenter = sa;
				}				
			}
		}
		
		if (newCenter!=null) {
			ac.getElements().remove(center);
		}
	}

	private void assignCluster(ArrayList<AttributeCluster> clusters, ArrayList<SRCycleAttribute> objects) {	
		for (SRCycleAttribute o : objects){			
			AttributeCluster c = this.findCluster(o, clusters);
			if (!c.isCenter(o)) c.addElement(o);
		}

	}
	
	private ArrayList<AttributeCluster> initializeTrainingClusters(ArrayList<SRCycleAttribute> objects, int isKnownFailure){
//		Collections.sort(objects);
		ArrayList<AttributeCluster> clusters = new ArrayList<AttributeCluster>();
			
		ArrayList<Integer> addedAttrPosList = new ArrayList<Integer>();
		ArrayList<Integer> addedEventSizeList = new ArrayList<Integer>();
		for (int i=0; i<objects.size(); i++){
			SRCycleAttribute sa = objects.get(i);
//			Debug.println(sa);
			int eventSize = sa.getEvents().size();
			if (!addedEventSizeList.contains(eventSize)){
				addedEventSizeList.add(eventSize);					
				addedAttrPosList.add(i);	
				
				AttributeCluster newCluster = new AttributeCluster(isKnownFailure);
				newCluster.setCenter(sa);
				clusters.add(newCluster);
			}
		}
				
//		Debug.println(clusters.size() + " clusters initialized ");
//		Debug.exit();
		return clusters;
	}
	
	private ArrayList<SRCycleAttribute> createTrainingObjects(String dataSrc, int isKnownFailure) throws IOException{
		Debug.println("creat training data from " + dataSrc);
		ArrayList<SRCycleAttribute> objects = new  ArrayList<SRCycleAttribute>();
		ArrayList<String> dataStr = File_Util.file_content_to_strings(dataSrc);
		
		for(String ds : dataStr){
			SRCycleAttribute sa = createAttribute(ds, isKnownFailure);			
			objects.add(sa);
		}
		
		return objects;
	}
	
	private SRCycleAttribute createAttribute(String ds, int isKnownFailure){	
		StringTokenizer dsSt= new StringTokenizer(ds, " ");
		
		String id = dsSt.nextToken();	
		String rep = dsSt.nextToken();
		
		String eventStr = dsSt.nextToken();
		
		ArrayList<String> events = new ArrayList<String>();
		
		StringTokenizer st = new StringTokenizer(eventStr, ",");
		while (st.hasMoreTokens()){
			String t = st.nextToken();
			if (t.contains("enter")) t = "enter";
			events.add(t);
		}			
//		Debug.println("---\nid=" + id + ", occ=" + occ + ", rep=" + rep + ", events=" + events);
				
		int intRep = Integer.parseInt(rep);
		
		SRCycleAttribute sa = new SRCycleAttribute(id, intRep, events);
		
//		Debug.println(sa.toString());
		return sa;
	}
	
	private AttributeCluster findCluster(SRCycleAttribute src, ArrayList<AttributeCluster> clusters){
//		Debug.println("\n---For attr: "  + src);
		double tmpDis = Double.MAX_VALUE;
		int tmpClusterToAssign = clusters.size();
		for (int i=0; i<clusters.size(); i++){
			AttributeCluster c = clusters.get(i);
			if (c.getCenter()==null)
				throw new SIRANAException("Cluster not initialized!");

			double disToCluster = computeDistanceToCluster(src, c);
//			Debug.println( "\ndisToCluster=" + disToCluster + ": To cluster " + c);
			if (disToCluster < tmpDis){
				tmpDis = disToCluster;
				tmpClusterToAssign = i;
//				Debug.println("tmpPos =" + tmpClusterToAssign);
			}			
		}
		
		AttributeCluster targetCluster = clusters.get(tmpClusterToAssign);
//		targetCluster.addElement(src);
//		Debug.println( "###Attr: " + src + " ADD to as " + targetCluster.isKnownFailure() + "\n");
		return targetCluster;
	}
	
	private double computeDistanceToCluster(SRCycleAttribute src, AttributeCluster cluster){
		double dis = 0;
		
//		for (SRCycleAttribute c: cluster.getElements()){
//			dis += computeAttributeDistance(src, c);
//		}

//		Debug.println( "\ndisToCluster=" + dis + ": To cluster " + cluster);
//		double disToCluster = dis/cluster.getElements().size();
		double disToCluster  = computeAttributeDistance(src, cluster.getCenter());
		return disToCluster;
	}
	
	private double computeAttributeDistance(SRCycleAttribute src1, SRCycleAttribute src2){
//		double eventDis = this.computeEventDis(src1, src2);
//		
//		double attrDis = Math.sqrt(eventDis * eventDis);
		return  this.computeEventModeDis(src1, src2);
	}
	
	private double computeEventModeDis(SRCycleAttribute src1, SRCycleAttribute src2){
		ArrayList<String>
		events1 = src1.getEvents(),
		events2 = src2.getEvents();
		double 
			eventDis = new LevenshteinDistance().computeStringList(events1, events2);
					//new LevenshteinDistance().computeNormalizedEditDistance(src1, src2);
					//new StringHammingDistance().computeNormalizedHD(events1, events2);
		return eventDis/(events1.size() + events2.size());
	}
}
